package se.webbzon.boltzmann.minigame.page;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;


public class PageParser {
	
	// The size of buffer used for parsing special characters and tags
	private static final int bufferSize = 15;
	
	// A map of all the special characters 
	private final Map<char[],Character> specialSymbols;
	
	// A map of all the available tags
	private final Map<char[],Tag> tags;
	
	/*============================================================
	Constructors
	============================================================*/
	
	public PageParser() {
		specialSymbols = new TreeMap<char[],Character>(new BufferComparator());
		tags = new TreeMap<char[],Tag>(new BufferComparator());
		addSpecialCharacter("amp", '&');
		addSpecialCharacter("gt", '>');
		addSpecialCharacter("lt", '<');
		addTag(new PlainTag());
		addTag(new BoldTag());
		addTag(new BoldItalicTag());
		addTag(new ItalicTag());
		addTag(new HeaderTag());
		addTag(new ChapterTag());
		addTag(new TitleTag());
		addTag(new ImageTag());
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Adds a special character to this page parser. **/
	public void addSpecialCharacter(String entity, char c) {
		specialSymbols.put(entity.toLowerCase().toCharArray(), c);
	}
	
	/** Removes a special character from this page parser. **/
	public void removeSpecialCharacter(String entity) {
		specialSymbols.remove(entity.toLowerCase().toCharArray());
	}
	
	/** Adds a special character to this page parser. **/
	public void addTag(Tag tag) {
		tags.put(tag.getName().toLowerCase().toCharArray(), tag);
	}
	
	/** Removes a special character from this page parser. **/
	public void removeTag(Tag tag) {
		tags.remove(tag.getName().toLowerCase().toCharArray());
	}
	
	/** Parses the provided URL into a list of page items. **/
	public List<PageItem> parse(PageLayout layout, URL url) throws IOException {
		InputStream is = url.openStream();
		List<PageItem> items = parse(layout,is);
		is.close();
		return items;
	}
	
	/** Parses the provided input stream into a list of page items. **/
	public List<PageItem> parse(PageLayout layout, InputStream is) throws IOException {
		return parse(layout,new InputStreamReader(is));
	}
	
	/** Parses the provided string into a list of page items. **/
	public List<PageItem> parse(PageLayout layout, String info) throws IOException {
		return parse(layout, new StringReader(info));
	}
	
	/** Parses the provided reader into a list of page items. **/
	public List<PageItem> parse(PageLayout layout, Reader reader) throws IOException {
		char[] buffer = new char[bufferSize+1];
		char[] tag = new char[bufferSize+1];
		int bufferPtr = 0;
		char c;
		boolean withinAnd = false;
		boolean withinTag = false;
		boolean endTag = false;
		StringBuilder builder = null; 
		final List<PageItem> items = new ArrayList<PageItem>();
		
		while ((c = (char) reader.read()) != (char) -1) {
			if (withinAnd) {
				if (c == ';') {
					// Lookup special entity
					buffer[bufferPtr] = 0;
					char s = special(buffer);
					if (s == (char) -1)
						throw new IOException("Illegal symbol name: " + String.valueOf(buffer, 0, bufferPtr));
					builder.append(s);
					withinAnd = false;
				} else if (bufferPtr < bufferSize) {
					// Buffer character
					buffer[bufferPtr++] = c;
				} else throw new IOException("Illegal symbol name: " + String.valueOf(buffer, 0, bufferPtr));
			
			} else if (withinTag) {
				if (c == '>') {
					// Lookup tag
					buffer[bufferPtr] = 0;
					if (endTag) {
						if (compareBuffers(buffer,tag) != 0)
							throw new IOException("Only single depth tags are supported!");
						Tag t = tags.get(buffer);
						if (t == null)
							throw new IOException("Illegal tag name: " + String.valueOf(buffer, 0, bufferPtr));
						else
							items.add(t.createItem(layout, builder.toString()));
						endTag = false; 
					} else {
						System.arraycopy(buffer, 0, tag, 0, bufferPtr+1);
						builder = new StringBuilder();
					}
					withinTag = false;
				} else if (bufferPtr < bufferSize) {
					// Buffer character
					if (bufferPtr == 0 && c == '/')
						endTag = true;
					else
						buffer[bufferPtr++] = c;
				} else throw new IOException("Illegal tag name: " +String.valueOf(buffer, 0, bufferPtr));
			
			} else if (c == '&') {
				bufferPtr = 0;
				withinAnd = true;
			
			} else if (c == '<') {
				bufferPtr = 0;
				withinTag = true;
			} else 
				builder.append(c);
		}
		return items;
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Compares the left and right character array according to
	 * the interface comparator<char[]>. **/
	private static int compareBuffers(char[] left, char[] right) {
		for (int i = 0; i < left.length; i++) {
			if (i < right.length) {
				final char l = left[i];
				final char r = right[i];
				final int d = 	Character.toLowerCase(l) - 
								Character.toLowerCase(r);
				if (d != 0)
					return d;
			} else if (left[i] == 0) {
				return 0;
			} else
				return 1;
		}
		
		// Compare the remaining parts
		if (right.length > left.length) {
			return right[left.length] == 0 ? 0 : -1;
		} else
			return 0;
	}
	
	/** Returns the special character associated with the provided buffer array. **/
	private char special(char[] buffer) {
		Character c = specialSymbols.get(buffer);
		if (c == null)
			return (char) -1;
		else
			return c;
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	/** An implementation of a character array comparator. **/
	private static final class BufferComparator implements Comparator<char[]> {

		@Override public int compare(char[] left, char[] right) {
			return compareBuffers(left,right);
		}
		
	}

}
